using System;
using System.Collections.Generic;
using System.Diagnostics;
using Dida.Dbg;

namespace Dida
{
	public class World
	{
		CollisionTree staticCollision;
		DebugMesh staticCollisionDebugMesh = null;

		HashSet<Actor> actors = new HashSet<Actor>();
		SpatialHash actorsSpatialHash = new SpatialHash();

		List<Actor> actorsToAdd = new List<Actor>();
		List<Actor> actorsToAddBackbuffer = new List<Actor>();
		List<Actor> actorsToRemove = new List<Actor>();
		List<Actor> actorsToRemoveBackbuffer = new List<Actor>();

		List<AniSet> rootAniSets = new List<AniSet>();

		bool running = false;

		public World()
		{
		}

		public void AddActor(Actor actor)
		{
			if (!running)
			{
				actor.World = this;
				actors.Add(actor);
			}
			else
			{
				actorsToAdd.Add(actor);
			}
		}

		public void RemoveActor(Actor actor)
		{
			throw new NotImplementedException();
			/*if (!running)
			{
				actors.Remove(actor);
				actorsSpatialHash.RemoveObject(actor);
			}
			else
			{
				actorsToRemove.Add(actor);
			}*/
		}

		public void Start()
		{
			Debug.Assert(!running,"You shouldn't call add multiple times (unless you called Stop first)");
			Debug.Assert(actorsToAdd.Count == 0,"actorsToAdd should be zero when we're not running");
			Debug.Assert(actorsToRemove.Count == 0,"actorsToRemove should be zero when we are not running");

			running = true;

			foreach (AniSet aniSet in rootAniSets)
				aniSet.Start();

			foreach (Actor actor in actors)
				actorsSpatialHash.AddObject(actor);
			foreach (Actor actor in actors)
				actor.Start();

			Debug.Assert(actorsToRemove.Count == 0,
				"It's not allowed to remove actors in Start (adding is though)");
			FlushAddActors();
		}

		public void Stop()
		{
			if (!running)
				return;

			FlushAddActors();

			foreach (Actor actor in actors)
				actor.Stop();

			Debug.Assert(actorsToAdd.Count == 0,
				"It's not allowing to add actors in Stop(removing is though)");
			FlushRemoveActors();

			running = false;
		}

		private void FlushAddActors()
		{
			Debug.Assert(running, "FlushAddActors should only be called while running");

#if DEBUG
			int dbgNToRemove = actorsToRemove.Count;
#endif

			while (actorsToAdd.Count != 0)
			{
				List<Actor> backup = actorsToAdd;
				actorsToAdd = actorsToAddBackbuffer;
				actorsToAddBackbuffer = backup;

				Debug.Assert(actorsToAdd.Count == 0);

				foreach (Actor actor in actorsToAddBackbuffer)
				{
					actors.Add(actor);
					actorsSpatialHash.AddObject(actor);
					actor.Start();
				}
				actorsToAddBackbuffer.Clear();

#if DEBUG
				Debug.Assert(actorsToRemove.Count == dbgNToRemove,
					"It's not allowed to remove actors in Start (adding is though)");
#endif
			}
		}

		private void FlushRemoveActors()
		{
			Debug.Assert(running, "FlushRemoveActors should only be called while running");

#if DEBUG
			int dbgNToAdd = actorsToAdd.Count;
#endif

			while (actorsToRemove.Count != 0)
			{
				List<Actor> backup = actorsToRemove;
				actorsToRemove = actorsToRemoveBackbuffer;
				actorsToRemoveBackbuffer = backup;

				Debug.Assert(actorsToRemove.Count == 0);

				foreach (Actor actor in actorsToRemoveBackbuffer)
				{
					actor.Stop();
					actors.Remove(actor);
					actorsSpatialHash.RemoveObject(actor);
				}
				actorsToRemoveBackbuffer.Clear();

#if DEBUG
				Debug.Assert(actorsToRemove.Count == dbgNToAdd,
					"It's not allowed to add actors in Stop (removing is though)");
#endif
			}
		}

		public void Update(float timeDelta)
		{
			Debug.Assert(running);

			FlushAddActors();
			FlushRemoveActors();

			foreach(AniSet aniSet in rootAniSets)
				aniSet.Update(timeDelta);

			foreach (Actor actor in actors)
			{
				actor.Update(timeDelta);
				actorsSpatialHash.UpdateObject(actor);
			}
		}

		public CollisionTree StaticCollision
		{
			get { return staticCollision; }
			set { staticCollision = value; }
		}

		public IList<AniSet> RootAniSets
		{
			get { return rootAniSets; }
		}

		public WorldCollisionCache CreateCollisionCache(Sphere sphere,
			float startTime, float endTime)
		{
			throw new NotImplementedException();
		}

		public bool TestForCollision(Sphere sphere, Vector3D dir, float startTime,
			float endTime, out WorldCollisionResult res)
		{
			CollisionResult collisionRes;
			bool collisionOccured = staticCollision.TestForCollision(
				sphere, dir, out collisionRes);
			if (collisionOccured)
			{
				res = new WorldCollisionResult();
				res.normal = collisionRes.normal;
				res.dist = collisionRes.dist;
				res.collisionObject = null;
				return true;
			}
			else
			{
				res = null;
				return false;
			}
		}

		public void DrawDebug(IDebugDrawTarget target)
		{
			if (staticCollision != null)
			{
				if (staticCollisionDebugMesh == null)
				{
					staticCollisionDebugMesh = DebugDraw.CreateDebugMesh();
					staticCollisionDebugMesh.Begin();
					staticCollision.DrawToDebugMesh(staticCollisionDebugMesh);
					staticCollisionDebugMesh.End();
				}

				target.DrawDebugMesh(staticCollisionDebugMesh);
			}

			foreach (Actor actor in actors)
			{
				actor.DrawDebug(target);
			}
		}

		// temporary
		public Viewpoint viewpoint;
	}

	public class WorldCollisionResult : CollisionResult
	{
		public ICollisionObject collisionObject;
	}

	public class WorldCollisionCache
	{
	}
}